#include <sdk.h>
#include <wx/tokenzr.h>
#include <M_Code_StyledTextCtrl.h>
#include "parser_base.h"

#define CC_PARSER_BASE_DEBUG_OUTPUT 0

ParserCommon::EFileType ParserCommon::FileType( const wxString& filename, bool force_refresh ) {
  static bool cfg_read  = false;
  static bool empty_ext = true;
  static wxArrayString header_ext;
  static wxArrayString source_ext;
  if( !cfg_read || force_refresh ) {
    ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "code_completion" ) );
    empty_ext = cfg->ReadBool( _T( "/empty_ext" ), true );
    wxString header_ext_str = cfg->Read( _T( "/header_ext" ), _T( "h,hpp,hxx,hh,h++,tcc,tpp,xpm" ) );
    wxString source_ext_str = cfg->Read( _T( "/source_ext" ), _T( "c,cpp,cxx,cc,c++" ) );
    header_ext.Clear();
    wxStringTokenizer header_ext_tknzr( header_ext_str, _T( "," ) );
    while( header_ext_tknzr.HasMoreTokens() ) {
      header_ext.Add( header_ext_tknzr.GetNextToken().Trim( false ).Trim( true ).Lower() );
    }
    source_ext.Clear();
    wxStringTokenizer source_ext_tknzr( source_ext_str, _T( "," ) );
    while( source_ext_tknzr.HasMoreTokens() ) {
      source_ext.Add( source_ext_tknzr.GetNextToken().Trim( false ).Trim( true ).Lower() );
    }
    cfg_read = true;
  }
  if( filename.IsEmpty() ) {
    return ParserCommon::ftOther;
  }
  const wxString file = filename.AfterLast( wxFILE_SEP_PATH ).Lower();
  const int pos  = file.Find( _T( '.' ), true );
  wxString ext;
  if( pos != wxNOT_FOUND ) {
    ext = file.SubString( pos + 1, file.Len() );
  }
  if( empty_ext && ext.IsEmpty() ) {
    return ParserCommon::ftHeader;
  }
  for( size_t i = 0; i < header_ext.GetCount(); ++i ) {
    if( ext == header_ext[i] ) {
      return ParserCommon::ftHeader;
    }
  }
  for( size_t i = 0; i < source_ext.GetCount(); ++i ) {
    if( ext == source_ext[i] ) {
      return ParserCommon::ftSource;
    }
  }
  return ParserCommon::ftOther;
}

ParserBase::ParserBase() {
  m_TokenTree     = new TokenTree;
  m_TempTokenTree = new TokenTree;
}

ParserBase::~ParserBase() {
  CC_LOCKER_TRACK_TT_MTX_LOCK( s_TokenTreeMutex )
  Delete( m_TokenTree );
  Delete( m_TempTokenTree );
  CC_LOCKER_TRACK_TT_MTX_UNLOCK( s_TokenTreeMutex )
}

TokenTree* ParserBase::GetTokenTree() const {
  return m_TokenTree;
}

bool ParserBase::ParseFile( const wxString& filename, bool isGlobal, bool ) {
  return Reparse( filename, !isGlobal );
}

bool ParserBase::Reparse( const wxString& file, M_Code_unused bool isLocal ) {
  FileLoader* loader = new FileLoader( file );
  ( *loader )();
  ParserThreadOptions opts;
  opts.useBuffer = false;
  opts.parentIdxOfBuffer = -1;
  opts.initLineOfBuffer = -1;
  opts.bufferSkipBlocks = false;
  opts.bufferSkipOuterBlocks = false;
  opts.isTemp = false;
  opts.followLocalIncludes = true;
  opts.followGlobalIncludes = true;
  opts.wantPreprocessor = true;
  opts.parseComplexMacros = true;
  opts.platformCheck = true;
  opts.handleFunctions = true;
  opts.handleVars = true;
  opts.handleClasses = true;
  opts.handleEnums = true;
  opts.handleTypedefs = true;
  opts.storeDocumentation = true;
  opts.loader = loader;
  m_TokenTree->ReserveFileForParsing( file, true );
  ParserThread* pt = new ParserThread( this, file, true, opts, m_TokenTree );
  bool success = pt->Parse();
  delete pt;
  return success;
}

bool ParserBase::ParseBuffer( const wxString& buffer,
                              bool isLocal,
                              bool bufferSkipBlocks,
                              bool isTemp,
                              const wxString& filename,
                              int parentIdx,
                              int initLine ) {
  ParserThreadOptions opts;
  opts.useBuffer            = true;
  opts.fileOfBuffer         = filename;
  opts.parentIdxOfBuffer    = parentIdx;
  opts.initLineOfBuffer     = initLine;
  opts.bufferSkipBlocks     = bufferSkipBlocks;
  opts.isTemp               = isTemp;
  opts.followLocalIncludes  = true;
  opts.followGlobalIncludes = true;
  opts.wantPreprocessor     = m_Options.wantPreprocessor;
  opts.parseComplexMacros   = true;
  opts.platformCheck        = true;
  opts.handleFunctions      = true;
  opts.storeDocumentation   = m_Options.storeDocumentation;
  ParserThread thread( this, buffer, isLocal, opts, m_TokenTree );
  bool success = thread.Parse();
  return success;
}

void ParserBase::AddIncludeDir( const wxString& dir ) {
  if( dir.IsEmpty() ) {
    return;
  }
  wxString base = dir;
  if( base.Last() == _T( '/' ) ) {
    base.RemoveLast();
  }
  if( !wxDir::Exists( base ) ) {
    return;
  }
  if( m_IncludeDirs.Index( base ) == wxNOT_FOUND ) {
    m_IncludeDirs.Add( base );
  }
}

wxString ParserBase::FindFirstFileInIncludeDirs( const wxString& file ) {
  wxString FirstFound = m_GlobalIncludes.GetItem( file );
  if( FirstFound.IsEmpty() ) {
    wxArrayString FoundSet = FindFileInIncludeDirs( file, true );
    if( FoundSet.GetCount() ) {
      FirstFound = UnixFilename( FoundSet[0] );
      m_GlobalIncludes.AddItem( file, FirstFound );
    }
  }
  return FirstFound;
}

wxArrayString ParserBase::FindFileInIncludeDirs( const wxString& file, bool firstonly ) {
  wxArrayString FoundSet;
  for( size_t idxSearch = 0; idxSearch < m_IncludeDirs.GetCount(); ++idxSearch ) {
    wxString base = m_IncludeDirs[idxSearch];
    wxFileName tmp = file;
    NormalizePath( tmp, base );
    wxString fullname = tmp.GetFullPath();
    if( wxFileExists( fullname ) ) {
      FoundSet.Add( fullname );
      if( firstonly ) {
        break;
      }
    }
  }
  return FoundSet;
}

wxString ParserBase::GetFullFileName( const wxString& src, const wxString& tgt, bool isGlobal ) {
  wxString fullname;
  if( isGlobal ) {
    fullname = FindFirstFileInIncludeDirs( tgt );
    if( fullname.IsEmpty() ) {
      wxString base = wxFileName( src ).GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
      fullname = FindFirstFileInIncludeDirs( base + tgt );
    }
  } else {
    wxFileName fname( tgt );
    wxFileName source( src );
    if( NormalizePath( fname, source.GetPath( wxPATH_GET_VOLUME ) ) ) {
      fullname = fname.GetFullPath();
      if( !wxFileExists( fullname ) ) {
        fullname.Clear();
      }
    }
  }
  return fullname;
}

size_t ParserBase::FindTokensInFile( const wxString& filename, TokenIdxSet& result, short int kindMask ) {
  result.clear();
  size_t tokens_found = 0;
  CC_LOCKER_TRACK_TT_MTX_LOCK( s_TokenTreeMutex )
  TokenIdxSet tmpresult;
  if( m_TokenTree->FindTokensInFile( filename, tmpresult, kindMask ) ) {
    for( TokenIdxSet::const_iterator it = tmpresult.begin(); it != tmpresult.end(); ++it ) {
      const Token* token = m_TokenTree->at( *it );
      if( token ) {
        result.insert( *it );
      }
    }
    tokens_found = result.size();
  }
  CC_LOCKER_TRACK_TT_MTX_UNLOCK( s_TokenTreeMutex )
  return tokens_found;
}
